using S7.Net;
using TrackSystem.Entities.Extensions;
using TrackSystem.IO.Alarm;
using TrackSystem.IO.Data;
using TrackSystem.IO.Models;

namespace TrackSystem.IO.Protocol
{
    /// <summary>
    ///  0-19 只读
    ///  20-22 只写
    ///  23--手动连接
    ///  24--手动关闭
    /// </summary>
    public class PLCS71200Protocol : ProtocolBase
    {
        private int startTicks;
        private int repeatTimes;    //写入操作重复次数

        private readonly int roAddr;
        private readonly int roLength;
        private readonly int rwAddr;
        private readonly int rwLength;
        private bool bSendW;     // 发送标志

        private int Station;
        private string plcIp;
        private Plc plc;
        bool isOpen;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="name"></param>
        /// <param name="ip"></param>
        public PLCS71200Protocol(string name, string ip, int station, int roAddr, int roLength, int rwAddr, int rwLength)
            : base()
        {
            Station = station;
            this.roAddr = roAddr;
            this.roLength = roLength;
            this.rwAddr = rwAddr;
            this.rwLength = rwLength;
            plcIp = ip;
            NeedInit = true;
            isOpen = false;
        }

        /// <summary>
        /// 生成读取寄存器的通讯帧
        /// </summary>
        /// <returns></returns>
        public override byte[] GenerateReadFrame(out int NeedRecvLen)
        {
            startTicks = Environment.TickCount;
            if (!isOpen)
            {
                Fail_R++;
            }
            else
            {
                ReadFrame();
            }
            if (NeedInit && !isOpen && Fail_R < 10) // 超过10次不再尝试打开
            {                
                InitPLC();
                if (isOpen)
                {
                    NeedInit = false;
                    Fail_R = 0;
                    for (int i = 0; i < rwLength; i++)
                    {
                        RegFactory.Instance.SetVal(RegNameList[i], 0);
                    }
                }
            }
            if (Fail_R >= 10)
            {
                Succ_R = 0;
            }
            NeedRecvLen = 0;
            return null;
        }

        /// <summary>
        /// 与PLC建立通讯
        /// </summary>
        /// <returns></returns>
        private void InitPLC() {
            plc = new(CpuType.S71200, plcIp, 0, 1); // 四个参数PLC型号，PLC IP地址，PLC 机架号，PLC 槽位号
            try
            {
                plc.Open(); // 尝试打开连接
                if (plc.IsConnected)
                {
                    // 读取操作
                    Fail_R = 0;
                    Console.WriteLine($"PLC S7-1200 " + plcIp + "连接成功");
                    isOpen = true;
                }
                else {
                    Console.WriteLine($"PLC S7-1200 " + plcIp + "连接失败");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"无法连接到PLC:{plcIp}, {e.Message}");
            }
        }
        /// <summary>
        /// 关闭PLC通讯
        /// </summary>
        /// <returns></returns>
        private void ClosePLC() {
            if (plc.IsConnected) {
                plc.Close();
                isOpen = false;
            }
        }

        /// <summary>
        /// 根据收到的内容进行处理
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public void ReadFrame()
        {           
            try {
                var buf = plc.ReadBytes(DataType.DataBlock, Station, roAddr*2, roLength*2); // 读取多个数据
                for (int i = 0; i < roLength; i++) {

                    var s1 = buf[i*2] * 256 + buf[i*2+1]; // buf[i]<< 8 | buf[i+1]<<16
                    RegFactory.Instance.SetVal(RegNameList[i], s1);
                }
                Succ_R++;
                Lapse = Environment.TickCount - startTicks;
            } catch (Exception e) {
                Succ_R = 0;
                Fail_R++;
                if (Fail_R > 10) {
                    isOpen = false; // 失败太多，直接关闭读取通道
                }
                Console.WriteLine($"读取PLC数据错误:{plcIp}, {e.Message}");
            }
            if (Succ_R >= 10)
            {
                Fail_R = 0;
            }
        }

        /// <summary>
        /// 生成写入寄存器的通讯帧
        /// </summary>
        /// <param name="NeedRecvLen"></param>
        /// <returns></returns>
        public override byte[] GenerateWriteFrame(out int NeedRecvLen)
        {
            // 待写入的数据队列非空 
            if (writeQueue.Count() > 0)
            {
                repeatTimes = 3;    // 写入的重复次数更新为3次
            }
            // 生成写入数据帧
            if (WriteFlag && repeatTimes-- > 0)
            {
                bSendW = false;
                startTicks = Environment.TickCount;
                while (writeQueue.TryDequeue(out AddressAndVal data))
                {               
                    byte[] buf = new byte[2];
                    buf[0] = (byte)((int)data.Value >> 8);
                    buf[1] = (byte)(data.Value);
                    try {
                        plc.WriteBytes(DataType.DataBlock, Station, data.Address*2+rwAddr*2, buf); // 写入多个数据
                        RegFactory.Instance.SetVal(RegNameList[data.Address+roLength], data.Value);
                        bSendW = true;
                    } catch (Exception e) {
                        Console.WriteLine($"写入PLC数据错误:{plcIp}, {e.Message}");
                    }
                }
                if (bSendW)
                {
                    WriteFlag = false;
                    Succ_W++;
                    Fail_W = 0;
                }
                else
                {
                    Fail_W++;
                }
                Lapse = Environment.TickCount - startTicks;
                // 上次没有收到正确的返回帧
                NeedRecvLen = 0;
                return null;
            }

            // 如果写入次数超过三次未成功，则将队列清空
            if (repeatTimes <= 0)
            {
                AlarmFactory.Instance.Insert("Write data error, more than three attempts",
                    AlarmCategory.COMM, Name, 0);

                // 标志位复位 重启下一个批次的读写任务
                WriteFlag = false;
            }
            NeedRecvLen = 0;
            return null;
        }

        /// <summary>
        /// 修改寄存器的值
        /// </summary>
        /// <param name="Index"></param>
        /// <param name="Value"></param>
        public override void PushRegValue(int Index, double Value)
        {
            Index -= roLength;  //寄存器存储顺序从0开始，先是只读存储器，后面是读写存储器

            if (Index >= 0 && Index < rwLength)
            {
                if (Index== rwLength-2 && Value==1) {
                    if (!plc.IsConnected) {
                        InitPLC();
                    }
                }
                else if (Index == rwLength-1 && Value == 1)
                {
                    ClosePLC();
                }
                else if(isOpen)
                {
                    writeQueue.Enqueue(new AddressAndVal(Index, Value));
                    WriteFlag = true;
                }
            }
        }
    }
}

